package com.ajavaer.framework.im.easemob;

import com.ajavaer.framework.AjavaerCache;
import com.ajavaer.framework.cache.type.CacheEngine;
import com.ajavaer.framework.cache.type.CacheItem;
import com.ajavaer.framework.common.tools.JsonTools;
import com.ajavaer.framework.common.tools.StringTools;
import com.ajavaer.framework.config.EasemobImConfig;
import com.ajavaer.framework.handle.RedisHandle;
import com.ajavaer.framework.im.easemob.exception.EasemobErrorHandler;
import com.ajavaer.framework.im.easemob.exception.EasemobException;
import com.ajavaer.framework.im.easemob.message.EasemobAccessToken;
import com.ajavaer.framework.im.easemob.message.EasemobApiMessage;
import com.ajavaer.framework.im.easemob.message.EasemobMessage;
import com.ajavaer.framework.im.easemob.model.EasemobFile;
import com.ajavaer.framework.im.easemob.model.TargetType;
import com.ajavaer.framework.im.easemob.model.UserModel;
import com.ajavaer.framework.im.easemob.request.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.env.Environment;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

@Component
@ConditionalOnProperty("ajavaer.im.easemob.org-name")
public class Easemob implements InitializingBean {
    private Logger log = LoggerFactory.getLogger(Easemob.class);
    @Resource
    private EasemobImConfig easemobImConfig;
    @Resource
    private Environment environment;

    private RedisHandle redisHandle = (RedisHandle) AjavaerCache.get(CacheEngine.redis);

    private RestTemplate restTemplate = new RestTemplate();

    private String host = "http://a1.easemob.com/{0}/{1}";

    private Boolean isDebug = true;

    /**
     * <pre>
     *     {
     *      "access_token":"YWMtY974wjRgEemd6VXKUBLMtQAAAAAAAAAAAAAAAAAAAAEBVjcQNFYR6brPKa3Y_7ezAgMAAAFpBoKr-QBPGgB1ey9AJQVB1rTEBdDIWr7rNUc9l-gmksw33UME40P59A",
     *      "expires_in":5184000,
     *      "application":"01563710-3456-11e9-bacf-29add8ffb7b3"
     *     }
     *
     * </pre>
     *
     * @return
     */
    public String token() throws EasemobException {
        EasemobMessage errorMessage = new EasemobMessage();
        String tokenString = redisHandle.get(easemobImConfig.getAccessTokenCacheKey(), key -> {
            if (isDebug) {
                log.debug("[Im Easemob] get token from server");
            }
            Map<String, Object> body = new HashMap<>();
            body.put("client_id", easemobImConfig.getClientId());
            body.put("client_secret", easemobImConfig.getClientSecret());
            body.put("grant_type", "client_credentials");
            EasemobAccessToken token = restTemplate.postForObject(host + "/token", body, EasemobAccessToken.class);
            if (token != null) {
                if (token.success()) {
                    return new CacheItem<>(key, token.getAccess_token(), token.getExpires_in(), TimeUnit.SECONDS);
                } else {
                    errorMessage.setError(token.getError());
                    errorMessage.setError_description(token.getError_description());
                }
            }
            return null;
        });
        if (StringTools.isNotBlank(tokenString)) {
            return tokenString;
        }
        throw new EasemobException(errorMessage);
    }

    /**
     * 批量用户注册
     *
     * @param userModels 用户信息
     * @return
     */
    public EasemobApiMessage<List<UserModel>> userRegister(List<UserModel> userModels) {
        ResponseEntity<EasemobApiMessage<List<UserModel>>> result = doPostExchange("/users", userModels);
        return result.getBody();
    }

    /**
     * 获取单个用户
     *
     * @param username 需要获取的 IM 用户名。
     * @return service_resource_not_found 表示用户不存在
     */
    public EasemobApiMessage<UserModel> userGet(String username) {
        ResponseEntity<EasemobApiMessage<UserModel>> result = doGetExchange("/users/" + username);
        return result.getBody();
    }

    /**
     * 批量获取用户
     *
     * @param limit  本次获取的用户条数
     * @param cursor 游标
     * @return
     */
    public EasemobApiMessage<List<UserModel>> userGetBatch(Integer limit, String cursor) {
        ResponseEntity<EasemobApiMessage<List<UserModel>>> result = doGetExchange("/users?limit=" + limit + "&cursor=" + StringTools.nvl(cursor));
        return result.getBody();
    }

    /**
     * 删除单个用户
     *
     * @param username 要删除的用户名
     * @return
     */
    public EasemobApiMessage<UserModel> userDelete(String username) {
        ResponseEntity<EasemobApiMessage<UserModel>> result = doDeleteExchange("/users/" + username);
        return result.getBody();
    }

    /**
     * 批量删除用户
     *
     * @param limit  本次获取的用户条数
     * @param cursor 游标
     * @return
     */
    public EasemobApiMessage<List<UserModel>> userDeleteBatch(Integer limit, String cursor) {
        ResponseEntity<EasemobApiMessage<List<UserModel>>> result = doDeleteExchange("/users?limit=" + limit + "&cursor=" + StringTools.nvl(cursor));
        return result.getBody();
    }

    /**
     * 修改用户密码
     *
     * @param username    用户名
     * @param newpassword 新密码
     * @return
     */
    public EasemobApiMessage userSetPassword(String username, String newpassword) {
        Map<String, String> body = new HashMap<>();
        body.put("newpassword", newpassword);
        ResponseEntity<EasemobApiMessage<String>> result = doPutExchange("/users/" + username + "/password", body);
        return result.getBody();
    }

    /**
     * 设置推送消息显示昵称,在 iOS APNS 推送时使用
     *
     * @param username 用户名
     * @param nickname 昵称
     * @return
     */
    public EasemobApiMessage<UserModel> userSetNickname(String username, String nickname) {
        Map<String, String> body = new HashMap<>();
        body.put("nickname", nickname);
        ResponseEntity<EasemobApiMessage<UserModel>> result = doPutExchange("/users/" + username, body);
        return result.getBody();
    }

    /**
     * 设置推送消息至客户端的方式，修改后及时有效。
     * 服务端对应不同的设置，向用户发送不同展示方式的消息。
     *
     * @param username                   用户名
     * @param notification_no_disturbing "true”仅通知，“false“通知以及消息详情
     * @return
     */
    public EasemobApiMessage<UserModel> userSetNotificationNoDisturbing(String username, boolean notification_no_disturbing) {
        Map<String, Object> body = new HashMap<>();
        body.put("notification_no_disturbing", notification_no_disturbing);
        ResponseEntity<EasemobApiMessage<UserModel>> result = doPutExchange("/users/" + username, body);
        return result.getBody();
    }

    /**
     * 设置 IM 用户免打扰，在免打扰期间，用户将不会收到消息通知。
     *
     * @param username                   用户名
     * @param notification_display_style 是否免打扰，“0”代表免打扰关闭，“1”免打扰开启
     * @return
     */
    public EasemobApiMessage<UserModel> userSetNotificationNoDisturbingAtTime(String username, boolean notification_display_style, Integer notification_no_disturbing_start, Integer notification_no_disturbing_end) {
        Map<String, Object> body = new HashMap<>();
        body.put("notification_display_style", notification_display_style ? 1 : 0);
        if (notification_display_style) {
            body.put("notification_no_disturbing_start", notification_no_disturbing_start);
            body.put("notification_no_disturbing_end", notification_no_disturbing_end);
        }
        ResponseEntity<EasemobApiMessage<UserModel>> result = doPutExchange("/users/" + username, body);
        return result.getBody();
    }


    /**
     * 禁用某个 IM 用户的账号，禁用后该用户不可登录，下次解禁后该账户恢复正常使用。
     *
     * @param username 要封禁的用户名
     * @return
     */
    public EasemobApiMessage<UserModel> userDeactivate(String username) {
        ResponseEntity<EasemobApiMessage<UserModel>> result = doPostExchange("/users/" + username + "/deactivate");
        return result.getBody();
    }

    /**
     * 解除 IM 用户账号的禁用操作，由禁用到解禁操作后，需要用户重新登录。
     *
     * @param username 要解封的用户名
     * @return
     */
    public EasemobApiMessage<String> userActivate(String username) {
        ResponseEntity<EasemobApiMessage<String>> result = doPostExchange("/users/" + username + "/activate");
        return result.getBody();
    }

    /**
     * 强制 IM 用户状态改为离线，用户需要重新登录才能正常使用。
     *
     * @param username 要强制下线的用户名
     * @return
     */
    public EasemobApiMessage<Map> userDisconnect(String username) {
        ResponseEntity<EasemobApiMessage<Map>> result = doPostExchange("/users/" + username + "/disconnect");
        return result.getBody();
    }

    /**
     * 获取用户在线状态
     *
     * @param username 要获取在线状态的用户名
     * @return "data":{"123456":"offline"}
     */
    public EasemobApiMessage<Map> userStatus(String username) {
        ResponseEntity<EasemobApiMessage<Map>> result = doGetExchange("/users/" + username + "/status");
        return result.getBody();
    }

    /**
     * 添加好友，好友必须是和自己在一个 APPkey 下的 IM 用户，每个用户的好友数量上限为1000。
     *
     * @param owner_username  要添加好友的用户名
     * @param friend_username 被添加好友的用户名
     * @return 返回friend用户信息
     */
    public EasemobApiMessage<UserModel> contactsAdd(String owner_username, String friend_username) {
        ResponseEntity<EasemobApiMessage<UserModel>> result = doPostExchange("/users/" + owner_username + "/contacts/users/" + friend_username);
        return result.getBody();
    }

    /**
     * 从 IM 用户的好友列表中移除一个 IM 用户。
     *
     * @param owner_username  要删除好友的用户名
     * @param friend_username 被删除好友的用户名
     * @return 返回friend用户信息
     */
    public EasemobApiMessage<UserModel> contactsDelete(String owner_username, String friend_username) {
        ResponseEntity<EasemobApiMessage<UserModel>> result = doDeleteExchange("/users/" + owner_username + "/contacts/users/" + friend_username);
        return result.getBody();
    }

    /**
     * 获取 IM 用户的好友列表
     *
     * @param owner_username 用户名
     * @return 返回username列表
     */
    public EasemobApiMessage<List<String>> contactsList(String owner_username) {
        ResponseEntity<EasemobApiMessage<List<String>>> result = doGetExchange("/users/" + owner_username + "/contacts/users");
        EasemobApiMessage<List<String>> body = result.getBody();
        body.setEntities(body.getData());
        return body;
    }

    /**
     * 获取 IM 用户的黑名单。
     *
     * @param owner_username 用户名
     * @return 黑名单username列表
     */
    public EasemobApiMessage<List<String>> blocksList(String owner_username) {
        ResponseEntity<EasemobApiMessage<List<String>>> result = doGetExchange("/users/" + owner_username + "/blocks/users");
        EasemobApiMessage<List<String>> body = result.getBody();
        body.setEntities(body.getData());
        return body;
    }

    /**
     * 向 IM 用户的黑名单列表中添加一个或者多个用户，黑名单中的用户无法给该 IM 用户发送消息，每个用户的黑名单人数上限为500。
     *
     * @param owner_username 要添加黑名单的用户名
     * @param username       被添加黑名单的用户名
     * @return 返回黑名单列表
     */
    public EasemobApiMessage<List<String>> blocksAdd(String owner_username, String username) {
        ResponseEntity<EasemobApiMessage<List<String>>> result = doPostExchange("/users/" + owner_username + "/blocks/users/" + username);
        return result.getBody();
    }

    /**
     * 从 IM 用户的黑名单中移除用户。将用户从黑名单移除后，恢复到好友，或者未添加好友的用户关系。可以正常的进行消息收发。
     *
     * @param owner_username 要添加黑名单的用户名
     * @param username       被添加黑名单的用户名
     * @return 返回黑名单列表
     */
    public EasemobApiMessage<List<String>> blocksDelete(String owner_username, String username) {
        ResponseEntity<EasemobApiMessage<List<String>>> result = doDeleteExchange("/users/" + owner_username + "/blocks/users/" + username);
        return result.getBody();
    }

    /**
     * 获取 IM 用户的离线消息数量。默认的离线消息数量上限为1200条。
     *
     * @param username 要获取在线状态的用户名
     * @return
     */
    public EasemobApiMessage<Map> offlineMsgCount(String username) {
        ResponseEntity<EasemobApiMessage<Map>> result = doGetExchange("/users/" + username + "/offline_msg_count");
        return result.getBody();
    }

    /**
     * 获取 IM 用户的离线消息状态，查看用户的离线消息离线消息的状态
     *
     * @param username 要获取在线状态的用户名
     * @param msgId    “对应的ID编号”，表示为对应的消息id， “delivered”表示状态为消息已投递，“undelivered”表示消息未投递
     * @return
     */
    public EasemobApiMessage<Map> offlineMsgCount(String username, String msgId) {
        ResponseEntity<EasemobApiMessage<Map>> result = doGetExchange("/users/" + username + "/offline_msg_count/" + msgId);
        return result.getBody();
    }

    /**
     * 发送文本消息
     *
     * @param from        表示消息发送者;无此字段Server会默认设置为“from”:“admin”，有from字段但值为空串(“”)时请求失败
     * @param target_type 发送的目标类型；users：给用户发消息，chatgroups：给群发消息，chatrooms：给聊天室发消息
     * @param target      发送的目标；注意这里需要用数组，数组长度建议不大于20，即使只有一个用户，也要用数组 ['u1']；给用户发送时数组元素是用户名，给群组发送时，数组元素是groupid
     * @param txt         发送的消息内容
     * @return
     */
    public EasemobApiMessage<Map> sendTxtMsg(String from, TargetType target_type, List<String> target, String txt) {
        return sendMsg(from, target_type, target, new EasemobMsgTxt(txt));
    }

    /**
     * 发送图片消息
     *
     * @param from        表示消息发送者;无此字段Server会默认设置为“from”:“admin”，有from字段但值为空串(“”)时请求失败
     * @param target_type 发送的目标类型；users：给用户发消息，chatgroups：给群发消息，chatrooms：给聊天室发消息
     * @param target      发送的目标；注意这里需要用数组，数组长度建议不大于20，即使只有一个用户，也要用数组 ['u1']；给用户发送时数组元素是用户名，给群组发送时，数组元素是groupid
     * @param easemobFile 上传成功后返回的对象
     * @return
     */
    public EasemobApiMessage<Map> sendImgMsg(String from, TargetType target_type, List<String> target, EasemobFile easemobFile, EasemobImgSize size) {
        EasemobMsgImg easemobMsgImg = new EasemobMsgImg(easemobFile, easemobImConfig);
        easemobMsgImg.setSize(size);
        return sendMsg(from, target_type, target, easemobMsgImg);
    }

    /**
     * 发送语音消息
     *
     * @param from        表示消息发送者;无此字段Server会默认设置为“from”:“admin”，有from字段但值为空串(“”)时请求失败
     * @param target_type 发送的目标类型；users：给用户发消息，chatgroups：给群发消息，chatrooms：给聊天室发消息
     * @param target      发送的目标；注意这里需要用数组，数组长度建议不大于20，即使只有一个用户，也要用数组 ['u1']；给用户发送时数组元素是用户名，给群组发送时，数组元素是groupid
     * @param easemobFile 上传成功后返回的对象
     * @param length      语音长度(秒)
     * @return
     */
    public EasemobApiMessage<Map> sendAudioMsg(String from, TargetType target_type, List<String> target, EasemobFile easemobFile, Integer length) {
        EasemobMsgAudio easemobMsgAudio = new EasemobMsgAudio(easemobFile, easemobImConfig);
        easemobMsgAudio.setLength(length);
        return sendMsg(from, target_type, target, easemobMsgAudio);
    }

    /**
     * 发送透传消息
     *
     * @param from        表示消息发送者;无此字段Server会默认设置为“from”:“admin”，有from字段但值为空串(“”)时请求失败
     * @param target_type 发送的目标类型；users：给用户发消息，chatgroups：给群发消息，chatrooms：给聊天室发消息
     * @param target      发送的目标；注意这里需要用数组，数组长度建议不大于20，即使只有一个用户，也要用数组 ['u1']；给用户发送时数组元素是用户名，给群组发送时，数组元素是groupid
     * @param action      指令
     * @return
     */
    public EasemobApiMessage<Map> sendCmdMsg(String from, TargetType target_type, List<String> target, String action) {
        return sendMsg(from, target_type, target, new EasemobMsgCmd(action));
    }

    public EasemobApiMessage<Map> sendMsg(String from, TargetType target_type, List<String> target, EasemobMsg msg) {
        return sendMsg(from, target_type, target, msg, null);
    }

    /**
     * 发送拓展消息
     *
     * @param from
     * @param target_type
     * @param target
     * @param msg
     * @param ext
     * @return
     */
    public EasemobApiMessage<Map> sendMsg(String from, TargetType target_type, List<String> target, EasemobMsg msg, Map<String, String> ext) {
        EasemobSend send = new EasemobSend(msg, ext);
        send.setTarget_type(target_type);
        send.setFrom(from);
        send.setTarget(target);
        ResponseEntity<EasemobApiMessage<Map>> result = doPostExchange("/messages", send);
        return result.getBody();
    }

    /**
     * 文件上传
     *
     * @param file
     * @return
     */
    public EasemobApiMessage<List<EasemobFile>> chatfilesUpload(File file) {
        ResponseEntity<EasemobApiMessage<List<EasemobFile>>> upload = upload(file);
        return upload.getBody();
    }

    /**
     * 文件下载
     *
     * @param file
     * @param processStream 文件流处理
     * @return
     */
    public EasemobApiMessage chatfilesDownload(EasemobFile file, Function<InputStream, EasemobMessage> processStream) {
        byte[] upload = download(file);
        if (upload[0] == 123) {
            try {
                return JsonTools.jsonToBean(new String(upload, "utf-8"), EasemobApiMessage.class);
            } catch (UnsupportedEncodingException e) {
                log.error(e.getMessage(), e);
            }
        }
        processStream.apply(new ByteArrayInputStream(upload));
        return new EasemobApiMessage();
    }

    private byte[] download(EasemobFile easemobFile) {
        String url = host + "/chatfiles/" + easemobFile.getUuid();
        if (isDebug) {
            log.debug("[Im Easemob] download url:" + url);
        }
        HttpEntity<Object> objectHttpEntity = new HttpEntity<>(downloadHeaders(easemobFile.getShareSecret(), easemobFile.getThumbnail()));
        ResponseEntity<byte[]> entity = restTemplate.exchange(url, HttpMethod.GET, objectHttpEntity, byte[].class);
        return entity.getBody();
    }


    private <T> ResponseEntity<EasemobApiMessage<T>> doDeleteExchange(String path) {
        return doExchange(path, HttpMethod.DELETE, null, true);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doGetExchange(String path) {
        return doExchange(path, HttpMethod.GET, null, true);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doPutExchange(String path, Object body) {
        return doExchange(path, HttpMethod.PUT, body, true);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doPostExchange(String path) {
        return doPostExchange(path, null, true);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doPostExchange(String path, Object body) {
        return doPostExchange(path, body, true);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doPostExchange(String path, Object body, Boolean authorization) {
        return doExchange(path, HttpMethod.POST, body, authorization);
    }

    private <T> ResponseEntity<EasemobApiMessage<T>> doExchange(String path, HttpMethod httpMethod, Object body, Boolean authorization) {
        String url = host + path;
        ParameterizedTypeReference<EasemobApiMessage<T>> responseType = new ParameterizedTypeReference<EasemobApiMessage<T>>() {
        };

        if (isDebug) {
            log.debug("[Im Easemob] doExchange url:" + url + " body:" + JsonTools.beanToJson(body));
        }
        return restTemplate.exchange(url, httpMethod, request(body, authorization), responseType);
    }

    /**
     * 文件上传
     *
     * @param file 文件
     * @return
     */
    private ResponseEntity<EasemobApiMessage<List<EasemobFile>>> upload(File file) {
        String url = host + "/chatfiles";
        FileSystemResource resource = new FileSystemResource(file);
        MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
        param.add("file", resource);
        ParameterizedTypeReference<EasemobApiMessage<List<EasemobFile>>> responseType = new ParameterizedTypeReference<EasemobApiMessage<List<EasemobFile>>>() {
        };
        if (isDebug) {
            log.debug("[Im Easemob] upload url:" + url + " file:" + file.getName());
        }
        return restTemplate.exchange(url, HttpMethod.POST, new HttpEntity<>(param, uploadHeaders(true)), responseType);
    }

    private HttpHeaders headers(Boolean authorization) {
        HttpHeaders headers = new HttpHeaders();//header参数
        if (authorization) {
            headers.add("Authorization", "Bearer " + token());
        }
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    private HttpHeaders uploadHeaders(Boolean restrict_access) {
        HttpHeaders headers = new HttpHeaders();//header参数
        headers.add("Authorization", "Bearer " + token());
        if (restrict_access != null && restrict_access) {
            headers.add("restrict-access", "true");
        }
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        return headers;
    }

    private HttpHeaders downloadHeaders(String shareSecret, String thumbnail) {
        HttpHeaders headers = new HttpHeaders();//header参数
        headers.add("Authorization", "Bearer " + token());
        if (StringTools.isNotBlank(shareSecret)) {
            headers.add("share-secret", shareSecret);
        }
        if (StringTools.isNotBlank(thumbnail)) {
            headers.add("thumbnail", "true");
        }
        headers.setContentType(MediaType.APPLICATION_JSON);
        return headers;
    }

    private <T> HttpEntity<T> request(T t, Boolean authorization) {
        return new HttpEntity<>(t, headers(authorization));
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.host = StringTools.format(host, easemobImConfig.getOrgName(), easemobImConfig.getAppName());
        restTemplate.setErrorHandler(new EasemobErrorHandler());
        restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
        this.isDebug = this.environment.getProperty("ajavaer.debug", Boolean.class, true);
        if (isDebug) {
            log.debug("Im Easemob init");
        }
    }
}
